﻿using System;
using System.Text;
using System.Collections;

using System.IO;
using System.Threading;
using System.Reflection;
using Microsoft.SPOT.IO;


namespace mfLogger
{
    /// <remarks>
    /// EXPERIMENTAL STATE!!!
    /// NetduinoPlus2: for file logging (sd card slot use) the Power supply has to be setted on usb xor 9VDcIn (not > 5v5!) (not @ the same time 
    /// NotSupportedException @File.Create
    /// references2: http://forums.netduino.com/index.php?/topic/4452-sd-card-mounting-throws-notsupportedexception/ 
    /// </remarks>
    public class mfPeriodicFileAppender : ILoggerAppender, IDisposable
    {
        #region Fields
        /// <summary>
        /// name of the target sd volume 
        /// </summary>
        const string C_STR_VOLUME_INFO = "SD";

        /// <summary>
        /// yyyyMMdd
        /// </summary>
        const string C_STR_LOG_FILE_DATE_FORMAT = "yyyyMMdd";
        /// <summary>
        /// _OpenSunTracer.log
        /// </summary>
        const string C_STR_LOG_FILE_NAME_FORMAT = "Default";
        /// <summary>
        /// 20000 ms --> 30 seconds
        /// </summary>
        const int C_FILE_WRITE_CYCLE = 30000;
        /// <summary>
        /// 5 sec.
        /// </summary>
        const int C_FILE_WRITE_INIT_CYCLE = 5000;

        private VolumeInfo _SdVolume = null;

        private StreamWriter _logFile = null;

        private mfLogMessageCollection _msgs = new mfLogMessageCollection(mfLogger.Instance.MessageCapacity);

        private Timer _timer = null;

        private object _sync = new object();

        private bool _isEnabled = true;

        #endregion

        #region CTOR
        public mfPeriodicFileAppender()
        {
            Name = this.GetType().Name;

            Initialize();
        }
        public mfPeriodicFileAppender(string name)
        {
            Name = name;

            Initialize();
        }

        private void Initialize()
        {
            Level = mfLogger.Instance.Level;

            RemovableMedia.Insert += RemovableMedia_Insert;
            RemovableMedia.Eject += RemovableMedia_Eject;

            var thread = new Thread(new ThreadStart(InitVolume));
            thread.Start();
        }

        private void InitVolume()
        {
            try
            {
                Thread.Sleep(1000);

                var volumes = VolumeInfo.GetVolumes();

                foreach (var volume in volumes)
                {
                    if (volume.Name == C_STR_VOLUME_INFO)
                    {
                        _SdVolume = volume;
                        DebugPrint("Logging media found!");
                        StartLogging();
                        break;
                    }
                }
            }
            catch
            {
                DebugPrint("no Logging media available!");
            }
        }

        #endregion

        #region Properties

        public bool IsSdConnected
        {
            get
            {
                return _SdVolume != null;
            }
        }

        #endregion

        #region Methods

        private void DebugPrint(string msg)
        {
            Microsoft.SPOT.Debug.Print(msg);
        }

        private void RemovableMedia_Insert(object sender, MediaEventArgs e)
        {
            //RemovableMedia_Insert is raised two times 1. Volu
            if (e.Volume.Name != C_STR_VOLUME_INFO)
            {
                DebugPrint("invalid media inserted '" + e.Volume.Name + "'");
                return;
            }

            if (_logFile != null)//Logging already initialized
            {
                return;
            }

            DebugPrint("RemovableMedia_Insert executing, new Volume " + e.Volume.Name
                + " (Totalsize=" + e.Volume.TotalSize + ";Freespace=" + e.Volume.TotalFreeSpace + ")");

            _SdVolume = e.Volume;

            StartLogging();

            DebugPrint("RemovableMedia_Insert executed");
        }

        private void StartLogging()
        {
            InitLogFile();

            _timer = new Timer(new TimerCallback(_TimerTick), null, C_FILE_WRITE_INIT_CYCLE, C_FILE_WRITE_CYCLE);

            DebugPrint("logging started");
        }

        private void RemovableMedia_Eject(object sender, MediaEventArgs e)
        {
            if (_logFile == null)//no Logging available...
            {
                return;
            }
            DebugPrint("RemovableMedia_Eject executing, Volume " + e.Volume.Name);

            StopLogging();

            DebugPrint("RemovableMedia_Eject executed, Volume " + e.Volume.Name);
        }

        private void StopLogging()
        {
            _timer.Dispose();
            _timer = null;

            _SdVolume = null;
            _logFile = null;
        }

        private void _TimerTick(object state)
        {
            if (_logFile == null
                || _SdVolume == null
                || _timer == null
                || mfLogger.Instance.Messages == null
                || (mfLogger.Instance.Messages != null && mfLogger.Instance.Messages.Count == 0))
            {
                return;
            }

            lock (_sync)
            {
                try
                {
                    var numMessages = _msgs.Count;
                    if (numMessages == 0)
                    {
                        return;
                    }


                    //write messages
                    for (int i = 0; i < numMessages; i++)
                    {
                        var msg = _msgs[i];

                        _logFile.WriteLine(msg.ToString());
                    }
                    _msgs.Clear();

                    //flush 
                    _logFile.Close();
                    _SdVolume.FlushAll();

                    //reinit _logfile
                    _logFile = null;
                    InitLogFile();

                    DebugPrint(numMessages + " Log-Messages wrote");
                }
                catch (IOException ioEx)
                {
                    _logFile = null;
                    _SdVolume = null;
                    DebugPrint("Error in InitLogFile():" + ioEx.Message);
                }
            }
        }

        public void InitLogFile()
        {
            if (_SdVolume == null)
            {
                DebugPrint("no _SdVolume initialized!");
                IsEnabled = false;
                return;
            }

            var dateString = DateTime.Now.ToString(C_STR_LOG_FILE_DATE_FORMAT);


            var logFilePath = Path.Combine(_SdVolume.RootDirectory, dateString + "_"
                + Name
                + ".log");

            try
            {
                if (File.Exists(logFilePath))
                {
                    var fsStr = File.OpenWrite(logFilePath);
                    _logFile = new StreamWriter(fsStr);
                    _logFile.BaseStream.Position = _logFile.BaseStream.Length - 1;

                    DebugPrint("logging on new media restarted, filename " + logFilePath);
                }
                else
                {
                    var fsStr = File.Create(logFilePath);
                    _logFile = new StreamWriter(fsStr);

                    DebugPrint("logging on new media started, filename " + logFilePath);
                }
            }
            catch (IOException ioEx)
            {
                DebugPrint("IO-Error in InitLogFile(): " + ioEx.ErrorCode);
            }
        }
        #endregion

        #region ILogger
        public string Name { get; set; }

        public bool IsEnabled
        {
            get
            {
                return _isEnabled;
            }
            set
            {
                _isEnabled = value;
            }
        }

        public enumLogMessageLevel Level
        {
            get;
            set;
        }

        public void Log(mfLogMessage msg)
        {
            HandleMessage(msg);
        }

        public void Debug(string text)
        {
            HandleMessage(new mfLogMessage(enumLogMessageLevel.Debug, text));
        }

        public void Info(string text)
        {
            HandleMessage(new mfLogMessage(text));
        }

        public void Warning(string text)
        {
            HandleMessage(new mfLogMessage(enumLogMessageLevel.Warning, text));
        }

        public void Error(Exception ex)
        {
            HandleMessage(new mfLogMessage(ex));
        }

        public void Error(string text)
        {
            HandleMessage(new mfLogMessage(enumLogMessageLevel.Error, text));
        }

        public void HandleMessage(mfLogMessage msg)
        {
            if (msg.Level >= Level)
            {
                lock (_sync)
                {
                    if ((_msgs.Count + 1) >= _msgs.Capacity)
                    {
                        //TODO: restart @ index = 0...
                        return;
                    }

                    _msgs.Add(msg);
                }
            }
        }

        #endregion

        #region IDisposable

        public void Dispose()
        {
            RemovableMedia.Insert -= RemovableMedia_Insert;
            RemovableMedia.Eject -= RemovableMedia_Eject;
        }

        #endregion
    }
}
